Steve Chamberlin contributes National Geographic Topo format.
authorrobertl <robertl@f51c46e8-681c-474f-0cfe-069cfd0219fb>
Thu, 15 Sep 2005 16:25:39 +0000 (16:25 +0000)
committerrobertl <robertl@f51c46e8-681c-474f-0cfe-069cfd0219fb>
Thu, 15 Sep 2005 16:25:39 +0000 (16:25 +0000)
gpsbabel/Makefile
gpsbabel/reference/tpo-sample.tpo [new file with mode: 0644]
gpsbabel/tpo.c [new file with mode: 0644]
gpsbabel/vecs.c

index 35ccd4f58c9ceb82099ed2277c9c4ee058451ce4..ff057fe2bd8dccdc582411e8609303be011428ee 100644 (file)
@@ -32,7 +32,7 @@ INSTALL_TARGETDIR=/usr/local/
 
 FMTS=magproto.o gpx.o geo.o mapsend.o mapsource.o garmin_tables.o \
        gpsutil.o pcx.o cetus.o copilot.o gpspilot.o magnav.o \
-       psp.o holux.o garmin.o tmpro.o tpg.o \
+       psp.o holux.o garmin.o tmpro.o tpg.o tpo.o \
        xcsv.o gcdb.o tiger.o internal_styles.o easygps.o quovadis.o \
        gpilots.o saroute.o navicache.o psitrex.o geoniche.o delgpl.o \
        ozi.o nmea.o text.o html.o palmdoc.o netstumbler.o hsa_ndv.o \
diff --git a/gpsbabel/reference/tpo-sample.tpo b/gpsbabel/reference/tpo-sample.tpo
new file mode 100644 (file)
index 0000000..6c3751e
Binary files /dev/null and b/gpsbabel/reference/tpo-sample.tpo differ
diff --git a/gpsbabel/tpo.c b/gpsbabel/tpo.c
new file mode 100644 (file)
index 0000000..9864c50
--- /dev/null
@@ -0,0 +1,805 @@
+/*
+    National Geographic Topo! Waypoint
+    Contributed to gpsbabel by Steve Chamberlin
+    
+    Copyright (C) 2005 Steve Chamberlin, slc at alum.mit.edu
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+
+ */
+
+#include "defs.h"
+#include <string.h>
+#include <ctype.h>
+#include "jeeps/gpsmath.h" /* for datum conversions */
+
+#define MYNAME "TPO"
+
+static char *dumpheader = NULL;
+static char *output_state = NULL;
+  
+static
+arglist_t tpo_args[] = {
+       { "dumpheader", &dumpheader, "(0/1) Display the file header bytes", 
+               "0", ARGTYPE_BOOL} , 
+       { "state", &output_state, "State map format to write, default=CA", 
+         "CA", ARGTYPE_STRING} , 
+       {0, 0, 0, 0, 0}
+};
+
+static FILE *tpo_file_in;
+static FILE *tpo_file_out;
+static void *mkshort_handle;
+
+static double output_track_lon_scale;
+static double output_track_lat_scale;
+
+static unsigned int track_out_count;
+static double first_track_waypoint_lat;
+static double first_track_waypoint_lon;
+static double track_length;
+static double last_waypoint_x;
+static double last_waypoint_y;
+static double last_waypoint_z;
+
+/*******************************************************************************/
+/*                                      READ                                   */
+/*******************************************************************************/
+static int
+tpo_fread(void *buff, size_t size, size_t members, FILE * fp) 
+{
+    size_t br;
+
+    br = fread(buff, size, members, fp);
+
+    if (br != members) {
+        fatal(MYNAME ": The input file does not look like a valid .TPO file.\n");
+    }
+
+    return (br);
+}
+
+static double
+tpo_fread_double(FILE *fp)
+{
+       unsigned char buf[8];
+       unsigned char sbuf[8];
+
+       tpo_fread(buf, 1, 8, fp);
+       le_read64(sbuf, buf);
+       return *(double *)sbuf;
+}
+
+static void
+tpo_fwrite_double(double x, FILE *fp)
+{
+       unsigned char *cptr = (unsigned char *)&x;
+       unsigned char cbuf[8];
+
+       le_read64(cbuf, cptr);
+       fwrite(cbuf, 8, 1, fp);
+}
+
+/* tpo_check_version_string()
+   Check the first bytes of the file for a version 3.0 header. */
+static void
+tpo_check_version_string()
+{
+
+       char string_size;
+       char* string_buffer;
+       char* v3_id_string = "TOPO! Ver";
+
+       /* read the id string */
+       tpo_fread(&string_size, 1, 1, tpo_file_in);
+       string_buffer = xmalloc(string_size+1);
+       tpo_fread(string_buffer, 1, string_size, tpo_file_in);
+       
+       /* terminate the strig */
+       string_buffer[string_size] = 0;
+
+       /* check for the presence of a 3.0-style id string */
+       if (strncmp(v3_id_string, string_buffer, strlen(v3_id_string)) == 0)
+       {
+               fatal(MYNAME ": gpsbabel can only read TPO version 2.7.7 or below; this file is %s\n", string_buffer);
+       }
+       else {
+               /* seek back to the beginning of the file */
+               fseek(tpo_file_in, -(string_size+1), SEEK_CUR);
+       }
+
+}
+
+static void
+/* tpo_dump_header_bytes(int header_size)
+   Write the first header_size bytes of the file to standard output
+   as a C array definition. */
+tpo_dump_header_bytes(int header_size)
+{
+       int i;
+       unsigned char* buffer = (unsigned char*)xmalloc(header_size);
+
+       tpo_fread(buffer, 1, header_size, tpo_file_in);
+
+       printf("unsigned char header_bytes[] = {\n");
+       
+       for (i=0; i<header_size; i++) {
+               if (i%8 == 0)
+                       printf("    ");
+               printf("0x%02X", buffer[i]);
+               if (i != header_size-1)
+                       printf(", ");
+               if (i%8 == 7)
+                       printf("\n");
+       }
+
+       printf("};\n");
+
+       xfree(buffer);
+}
+
+/* tpo_read_until_section()
+   Keep reading bytes from the file until the section name is encountered,
+   then seek backwards to the start of the section data. */
+static void
+tpo_read_until_section(const char* section_name)
+{
+       char byte;
+       unsigned int match_index = 0;
+       int header_size = 0;
+
+       while (1) {
+               tpo_fread(&byte, 1, 1, tpo_file_in);
+               header_size++;
+
+               if (byte == section_name[match_index]) {
+                       match_index++;
+                       if (match_index == strlen(section_name)) {
+                               fseek(tpo_file_in, -18, SEEK_CUR);
+                               header_size -= 18;
+                               if (dumpheader && dumpheader[0] == '1') {
+                                       fseek(tpo_file_in, -header_size, SEEK_CUR);
+                                       tpo_dump_header_bytes(header_size);
+                               }
+                               return;
+                       }
+               }
+               else {
+                       match_index = 0;
+               }
+       }
+}
+
+static void
+tpo_rd_init(const char *fname)
+{
+       if (doing_wpts || doing_rtes)
+       {
+               fatal(MYNAME ": this file format only supports tracks, not waypoints or routes.\n");
+       }
+       
+       tpo_file_in = xfopen(fname, "rb", MYNAME);
+       tpo_check_version_string();
+       tpo_read_until_section("CTopoRoute");
+}
+
+static void
+tpo_rd_deinit(void)
+{
+       fclose(tpo_file_in);
+}
+
+static void
+tpo_read(void)
+{
+       char buff[16];
+       short track_count, waypoint_count;
+       double first_lat, first_lon, lat_scale, lon_scale, amt;
+       short *lon_delta, *lat_delta;
+       int i, j;
+       route_head* track_temp;
+       waypoint* waypoint_temp;        
+
+       /* track count */
+       tpo_fread(&buff[0], 1, 2, tpo_file_in);
+       track_count = le_read16(&buff[0]);
+       
+       /* 4 unknown bytes */
+       tpo_fread(&buff[0], 1, 4, tpo_file_in);
+
+       /* chunk name: "CTopoRoute" */
+       tpo_fread(&buff[0], 1, 12, tpo_file_in);
+
+       for (i=0; i<track_count; i++) {
+
+               track_temp = route_head_alloc();
+               track_add_head(track_temp);
+
+               /* generate a generic track name */
+               sprintf(buff, "Track %d", i+1);
+               track_temp->rte_name = xstrdup(buff);
+
+               /* zoom level 1-5 visibility flags */
+               tpo_fread(&buff[0], 1, 10, tpo_file_in);
+
+               /* 8 bytes of zeros, meaning unknown */
+               tpo_fread(&buff[0], 1, 8, tpo_file_in);
+
+               /* 4 more unknown bytes, possibly sign flags for the longitude and latitude? */
+               tpo_fread(&buff[0], 1, 4, tpo_file_in);
+
+        /* read the position of the initial track point */
+               /* for some very odd reason, signs on longitude are swapped */
+        /* coordinates are in NAD27/CONUS datum                     */
+            
+        /* 8 bytes - longitude, sign swapped  */
+           first_lon = tpo_fread_double(tpo_file_in);
+
+        /* 8 bytes - latitude */
+           first_lat = tpo_fread_double(tpo_file_in);
+           
+        /* swap sign before we do datum conversions */
+           first_lon *= -1.0;
+
+               /* 8 unknown bytes: seems to be some kind of bounding box info */
+               tpo_fread(&buff[0], 1, 8, tpo_file_in);
+
+               /* number of route points */
+               tpo_fread(&buff[0], 1, 2, tpo_file_in);
+               waypoint_count = le_read16(&buff[0]);
+
+               /* allocate temporary memory for the waypoint deltas */
+               lon_delta = (short*)xmalloc(waypoint_count * sizeof(short));
+               lat_delta = (short*)xmalloc(waypoint_count * sizeof(short));
+
+               for (j=0; j<waypoint_count; j++) {
+                       
+                       /* get this point's longitude delta from the first waypoint */
+                       tpo_fread(&buff[0], 1, 2, tpo_file_in);
+                       lon_delta[j] = le_read16(&buff[0]);
+
+                       /* get this point's latitude delta from the first waypoint */
+                       tpo_fread(&buff[0], 1, 2, tpo_file_in);
+                       lat_delta[j] = le_read16(&buff[0]);
+               }
+
+               /* 8 bytes - longitude delta to degrees scale  */
+           lon_scale = tpo_fread_double(tpo_file_in);
+
+        /* 8 bytes - latitude delta to degrees scale */
+           lat_scale = tpo_fread_double(tpo_file_in);
+
+               /* 4 bytes: the total length of the route in feet*/
+               tpo_fread(&buff[0], 1, 4, tpo_file_in);
+
+               /* 2 unknown bytes */
+               tpo_fread(&buff[0], 1, 2, tpo_file_in);
+
+               /* 2 bytes: continuation marker */
+               tpo_fread(&buff[0], 1, 2, tpo_file_in);
+
+               /* multiply all the deltas by the scaling factors to determine the waypoint positions */
+               for (j=0; j<waypoint_count; j++) {
+                       
+                       waypoint_temp = waypt_new();
+
+                       /* convert incoming NAD27/CONUS coordinates to WGS84 */
+                       GPS_Math_Known_Datum_To_WGS84_M(
+                               first_lat-lat_delta[j]*lat_scale, 
+                               first_lon+lon_delta[j]*lon_scale,
+                               0.0,
+                               &waypoint_temp->latitude,
+                               &waypoint_temp->longitude,
+                               &amt,
+                               78);
+                       
+                       /* there is no elevation data for the waypoints */
+                       waypoint_temp->altitude = 0;
+
+                       route_add_wpt(track_temp, waypoint_temp);
+               }       
+
+               /* free temporary memory */
+               xfree(lon_delta);
+               xfree(lat_delta);
+       }
+}
+
+/*******************************************************************************/
+/*                                     WRITE                                   */
+/*******************************************************************************/
+
+/* tpo_write_file_header()
+   Write the appropriate header for the desired TOPO! state. 
+   
+   National Geographic sells about 75 different state and regional software 
+   programs called TOPO! that use the TPO format. Each one uses a different 
+   header data sequence. The header contains the name of the state maps, as well 
+   as some map scaling information and other data. In most cases, you can't open
+   a TPO file created by a different state/regional version of TOPO! than the one
+   you're using yourself. When writing a TPO file, it is therefore necessary to 
+   specify what TOPO! state product to create the file for. I believe that the 
+   TOPO! regional products can open TPO files created by the TOPO! state products
+   as long as the track data is within the area covered by the regional product.
+   As a result, it's only necessary to decide what state product output format to
+   use.
+
+   TO ADD SUPPORT FOR ANOTHER STATE:
+   1. Obtain an example .tpo file generated by the state product for which you wish
+   to add support. National Geographic MapXchange (http://maps.nationalgeographic.com/topo/search.cfm) 
+   is a good source of .tpo files.
+   2. Run gpsbabel using the "dumpheader" option of the TPO format converter, and
+   specifying a dummy ouput file. For example:
+     gpsbabel -t -i tpo,dumpheader=1 -f sample_file.tpo -o csv -F dummy.txt
+   This will write a snippet of C code containing the header bytes to the shell window.  
+   3. Add a new if() clause to tpo_write_file_header(). Copy the header bytes definition 
+   from the previous step.
+   4. Recompile gpsbabel.
+   5. You should now be able write TPO ouput in the new state's format. For example, if
+      you added support for Texas:
+     gpsbabel -t -i gpx -f input.gpx -o tpo,state="TX" -F output.tpo */
+static void
+tpo_write_file_header()
+{
+       int i;
+       
+       /* force upper-case state name */
+       for (i=0; i<(int)strlen(output_state); i++) {
+               output_state[i] = toupper(output_state[i]);
+       }
+       
+       if (strncmp("CA", output_state, 2) == 0) {
+
+               unsigned char header_bytes[] = {
+                       0x18, 0x43, 0x61, 0x6C, 0x69, 0x66, 0x6F, 0x72,
+                       0x6E, 0x69, 0x61, 0x20, 0x53, 0x68, 0x61, 0x64,
+                       0x65, 0x64, 0x20, 0x52, 0x65, 0x6C, 0x69, 0x65,
+                       0x66, 0x03, 0x43, 0x41, 0x31, 0x05, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x40, 0x5F, 0x40, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x40, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x80, 0x5C, 0x40, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x27, 0x43, 0x3A, 0x5C,
+                       0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20,
+                       0x46, 0x69, 0x6C, 0x65, 0x73, 0x5C, 0x54, 0x4F,
+                       0x50, 0x4F, 0x21, 0x5C, 0x54, 0x50, 0x4F, 0x5F,
+                       0x44, 0x41, 0x54, 0x41, 0x5C, 0x43, 0x41, 0x5F,
+                       0x44, 0x30, 0x31, 0x5C, 0x20, 0x43, 0x3A, 0x5C,
+                       0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20,
+                       0x46, 0x69, 0x6C, 0x65, 0x73, 0x5C, 0x54, 0x4F,
+                       0x50, 0x4F, 0x21, 0x5C, 0x54, 0x50, 0x4F, 0x5F,
+                       0x44, 0x41, 0x54, 0x41, 0x5C, 0x12, 0x43, 0x3A,
+                       0x5C, 0x54, 0x4F, 0x50, 0x4F, 0x21, 0x5C, 0x54,
+                       0x50, 0x4F, 0x5F, 0x44, 0x41, 0x54, 0x41, 0x5C,
+                       0x00, 0x00, 0x00, 0xDC, 0x30, 0x32, 0x30, 0x32,
+                       0x30, 0x32, 0x30, 0x33, 0x30, 0x33, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x32, 0x30, 0x32, 0x30, 0x32,
+                       0x30, 0x33, 0x30, 0x33, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x34, 0x30, 0x34, 0x30, 0x34, 0x30, 0x34,
+                       0x30, 0x36, 0x30, 0x36, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x35, 0x30, 0x35, 0x30, 0x34, 0x30, 0x36,
+                       0x30, 0x36, 0x30, 0x36, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x35,
+                       0x30, 0x35, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37,
+                       0x30, 0x37, 0x30, 0x38, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x35,
+                       0x30, 0x35, 0x30, 0x37, 0x30, 0x37, 0x30, 0x37,
+                       0x30, 0x38, 0x30, 0x38, 0x30, 0x39, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x35,
+                       0x31, 0x30, 0x30, 0x38, 0x30, 0x38, 0x30, 0x38,
+                       0x30, 0x39, 0x30, 0x39, 0x30, 0x39, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30,
+                       0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31,
+                       0x30, 0x39, 0x30, 0x39, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30,
+                       0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+                       0x30, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31,
+                       0x31, 0x31, 0x31, 0x31, 0x30, 0x39, 0x30, 0x39,
+                       0x0D, 0x55, 0x6E, 0x69, 0x74, 0x65, 0x64, 0x20,
+                       0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x40, 0x5F, 0x40, 0xBC, 0x23,
+                       0x63, 0xB5, 0xF9, 0x3A, 0x50, 0x40, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x80, 0x50, 0x40, 0x22, 0xE2,
+                       0xE6, 0x54, 0x32, 0x28, 0x22, 0x40, 0x0A, 0x43,
+                       0x61, 0x6C, 0x69, 0x66, 0x6F, 0x72, 0x6E, 0x69,
+                       0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5F,
+                       0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45,
+                       0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x5C,
+                       0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+                       0x40, 0x16, 0x2A, 0x47, 0x65, 0x6E, 0x65, 0x72,
+                       0x61, 0x6C, 0x20, 0x52, 0x65, 0x66, 0x65, 0x72,
+                       0x65, 0x6E, 0x63, 0x65, 0x20, 0x4D, 0x61, 0x70,
+                       0x00, 0x09, 0x3D, 0x00, 0x0C, 0x43, 0x41, 0x31,
+                       0x5F, 0x4D, 0x41, 0x50, 0x31, 0x5C, 0x53, 0x31,
+                       0x4C, 0xAF, 0x02, 0x15, 0x03, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x26, 0x40, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x24, 0x40, 0x84, 0x00, 0x78,
+                       0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x16,
+                       0x2A, 0x4E, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x61,
+                       0x6C, 0x20, 0x41, 0x74, 0x6C, 0x61, 0x73, 0x20,
+                       0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0xE8, 0x32,
+                       0x0D, 0x00, 0x02, 0x44, 0x41, 0x23, 0x01, 0x6C,
+                       0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+                       0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+                       0x3F, 0x3C, 0x00, 0x3C, 0x00, 0x01, 0x00, 0x00,
+                       0x00, 0x01, 0x00, 0x10, 0x2A, 0x35, 0x30, 0x30,
+                       0x4B, 0x20, 0x4D, 0x61, 0x70, 0x20, 0x53, 0x65,
+                       0x72, 0x69, 0x65, 0x73, 0xC0, 0xFE, 0x04, 0x00,
+                       0x02, 0x44, 0x46, 0x00, 0x01, 0x40, 0x01, 0xB5,
+                       0x2B, 0x4C, 0x55, 0x55, 0x55, 0xD5, 0x3F, 0xB5,
+                       0x2B, 0x4C, 0x55, 0x55, 0x55, 0xD5, 0x3F, 0x28,
+                       0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+                       0x00, 0x10, 0x2A, 0x31, 0x30, 0x30, 0x4B, 0x20,
+                       0x4D, 0x61, 0x70, 0x20, 0x53, 0x65, 0x72, 0x69,
+                       0x65, 0x73, 0x50, 0xC3, 0x00, 0x00, 0x02, 0x44,
+                       0x4B, 0x00, 0x00, 0x89, 0x01, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0xB0, 0x3F, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0xB0, 0x3F, 0x2D, 0x00, 0x2D,
+                       0x00, 0x0C, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x10,
+                       0x2A, 0x37, 0x2E, 0x35, 0x27, 0x20, 0x4D, 0x61,
+                       0x70, 0x20, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73,
+                       0x0F, 0x3C, 0x00, 0x00, 0x02, 0x44, 0x51, 0x00,
+                       0x00, 0x00, 0x01, 0x9A, 0x99, 0x99, 0x99, 0x99,
+                       0x99, 0x99, 0x3F, 0x9A, 0x99, 0x99, 0x99, 0x99,
+                       0x99, 0x89, 0x3F, 0x5A, 0x00, 0x2D, 0x00, 0x0D,
+                       0x00, 0x01, 0x00, 0x28, 0x00 
+               };
+
+               fwrite(header_bytes, sizeof(header_bytes), 1, tpo_file_out);
+       }
+       else if (strncmp("CT", output_state, 2) == 0 ||
+                        strncmp("MA", output_state, 2) == 0 ||
+                        strncmp("ME", output_state, 2) == 0 ||
+                        strncmp("NJ", output_state, 2) == 0 ||
+                        strncmp("NH", output_state, 2) == 0 ||
+                        strncmp("NY", output_state, 2) == 0 ||
+                        strncmp("RI", output_state, 2) == 0 ||
+                        strncmp("VT", output_state, 2) == 0) {
+               /* These 8 states are all covered in a single "Northeast" title */
+
+               unsigned char header_bytes[] = {
+                       0x1E, 0x4E, 0x6F, 0x72, 0x74, 0x68, 0x65, 0x61,
+                       0x73, 0x74, 0x65, 0x72, 0x6E, 0x20, 0x55, 0x53,
+                       0x41, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x64,
+                       0x20, 0x52, 0x65, 0x6C, 0x69, 0x65, 0x66, 0x03,
+                       0x4E, 0x45, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x54, 0x40, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x48, 0x40, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x80, 0x50, 0x40, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x43, 0x40, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x0B, 0x44, 0x3A, 0x5C, 0x4E, 0x45,
+                       0x31, 0x5F, 0x44, 0x30, 0x31, 0x5C, 0x12, 0x43,
+                       0x3A, 0x5C, 0x54, 0x4F, 0x50, 0x4F, 0x21, 0x5C,
+                       0x54, 0x50, 0x4F, 0x5F, 0x44, 0x41, 0x54, 0x41,
+                       0x5C, 0x12, 0x45, 0x3A, 0x5C, 0x54, 0x4F, 0x50,
+                       0x4F, 0x21, 0x5C, 0x54, 0x50, 0x4F, 0x5F, 0x44,
+                       0x41, 0x54, 0x41, 0x5C, 0x00, 0x00, 0x00, 0xFF,
+                       0x18, 0x01, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x37,
+                       0x30, 0x37, 0x30, 0x37, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x37, 0x30, 0x37, 0x30, 0x37, 0x30, 0x37,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0x34,
+                       0x30, 0x35, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37,
+                       0x30, 0x37, 0x30, 0x37, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0x33,
+                       0x30, 0x34, 0x30, 0x34, 0x30, 0x35, 0x30, 0x35,
+                       0x30, 0x36, 0x30, 0x36, 0x30, 0x36, 0x30, 0x36,
+                       0x30, 0x36, 0x30, 0x32, 0x30, 0x32, 0x30, 0x32,
+                       0x30, 0x33, 0x30, 0x33, 0x30, 0x34, 0x30, 0x34,
+                       0x30, 0x35, 0x30, 0x35, 0x30, 0x36, 0x30, 0x36,
+                       0x30, 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32,
+                       0x30, 0x32, 0x30, 0x32, 0x30, 0x33, 0x30, 0x33,
+                       0x30, 0x38, 0x30, 0x38, 0x30, 0x38, 0x30, 0x39,
+                       0x30, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x32, 0x30, 0x32, 0x30, 0x32,
+                       0x30, 0x33, 0x31, 0x30, 0x31, 0x30, 0x30, 0x38,
+                       0x30, 0x39, 0x30, 0x39, 0x30, 0x39, 0x30, 0x39,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30,
+                       0x31, 0x30, 0x31, 0x30, 0x30, 0x39, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+                       0x30, 0x30, 0x0D, 0x55, 0x6E, 0x69, 0x74, 0x65,
+                       0x64, 0x20, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5F, 0x40,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x4E, 0x40,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x50, 0x40,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x40,
+                       0x10, 0x4E, 0x6F, 0x72, 0x74, 0x68, 0x65, 0x61,
+                       0x73, 0x74, 0x65, 0x72, 0x6E, 0x20, 0x55, 0x53,
+                       0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54,
+                       0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0x48,
+                       0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x50,
+                       0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42,
+                       0x40, 0x16, 0x2A, 0x47, 0x65, 0x6E, 0x65, 0x72,
+                       0x61, 0x6C, 0x20, 0x52, 0x65, 0x66, 0x65, 0x72,
+                       0x65, 0x6E, 0x63, 0x65, 0x20, 0x4D, 0x61, 0x70,
+                       0x00, 0x09, 0x3D, 0x00, 0x0C, 0x4E, 0x45, 0x31,
+                       0x5F, 0x4D, 0x41, 0x50, 0x31, 0x5C, 0x53, 0x31,
+                       0x4C, 0x68, 0x03, 0x16, 0x03, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x2C, 0x40, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x24, 0x40, 0x8C, 0x00, 0x64,
+                       0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x16,
+                       0x2A, 0x4E, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x61,
+                       0x6C, 0x20, 0x41, 0x74, 0x6C, 0x61, 0x73, 0x20,
+                       0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0xE8, 0x32,
+                       0x0D, 0x00, 0x02, 0x44, 0x41, 0x0B, 0x01, 0x6C,
+                       0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+                       0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+                       0x3F, 0x3C, 0x00, 0x3C, 0x00, 0x01, 0x00, 0x00,
+                       0x00, 0x01, 0x00, 0x10, 0x2A, 0x35, 0x30, 0x30,
+                       0x4B, 0x20, 0x4D, 0x61, 0x70, 0x20, 0x53, 0x65,
+                       0x72, 0x69, 0x65, 0x73, 0xC0, 0xFE, 0x04, 0x00,
+                       0x02, 0x44, 0x46, 0xEA, 0x00, 0x40, 0x01, 0xB5,
+                       0x2B, 0x4C, 0x55, 0x55, 0x55, 0xD5, 0x3F, 0xB5,
+                       0x2B, 0x4C, 0x55, 0x55, 0x55, 0xD5, 0x3F, 0x28,
+                       0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+                       0x00, 0x10, 0x2A, 0x31, 0x30, 0x30, 0x4B, 0x20,
+                       0x4D, 0x61, 0x70, 0x20, 0x53, 0x65, 0x72, 0x69,
+                       0x65, 0x73, 0x50, 0xC3, 0x00, 0x00, 0x02, 0x44,
+                       0x4B, 0x00, 0x00, 0x89, 0x01, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0xB0, 0x3F, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0xB0, 0x3F, 0x2D, 0x00, 0x2D,
+                       0x00, 0x0C, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x10,
+                       0x2A, 0x37, 0x2E, 0x35, 0x27, 0x20, 0x4D, 0x61,
+                       0x70, 0x20, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73,
+                       0x0F, 0x3C, 0x00, 0x00, 0x02, 0x44, 0x51, 0x00,
+                       0x00, 0x00, 0x01, 0x9A, 0x99, 0x99, 0x99, 0x99,
+                       0x99, 0x99, 0x3F, 0x9A, 0x99, 0x99, 0x99, 0x99,
+                       0x99, 0x89, 0x3F, 0x5A, 0x00, 0x2D, 0x00, 0x0D,
+                       0x00, 0x01, 0x00, 0x28, 0x00
+               };
+
+               fwrite(header_bytes, sizeof(header_bytes), 1, tpo_file_out);
+       }
+
+       else {
+               fatal(MYNAME ": writing ouput for state \"%s\" is not currently supported.\n", output_state);
+       }
+}
+
+static void
+tpo_track_hdr(const route_head *rte)
+{
+       double amt;
+       unsigned char temp_buffer[8];
+       unsigned char visibility_flags[] = { 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00 };
+       unsigned char unknown1[] = { 0xFF, 0x00, 0x00, 0x00 };
+       unsigned char bounding_box[8] = { 0x00, 0x80, 0x00, 0x80, 0xFF, 0x7F, 0xFF, 0x7F };
+
+       waypoint* first_track_waypoint = (waypoint*) QUEUE_FIRST(&rte->waypoint_list);
+
+       /* zoom level 1-5 visibility flags */
+       fwrite(visibility_flags, 1, sizeof(visibility_flags), tpo_file_out);
+
+       /* 8 bytes of zeros, meaning unknown */
+       memset(temp_buffer, 0, sizeof(temp_buffer));
+       fwrite(temp_buffer, 1, sizeof(temp_buffer), tpo_file_out);
+
+       /* 4 more unknown bytes, possibly sign flags for the longitude and latitude? */
+       fwrite(unknown1, 1, sizeof(unknown1), tpo_file_out);
+
+       /* the starting point of the route */
+    /* convert lat/long to NAD27/CONUS datum */
+    GPS_Math_WGS84_To_Known_Datum_M(
+        first_track_waypoint->latitude,
+        first_track_waypoint->longitude,
+        first_track_waypoint->altitude,
+        &first_track_waypoint_lat,
+        &first_track_waypoint_lon,
+        &amt,
+        78);
+        
+    /* swap the sign back *after* the datum conversion */
+    first_track_waypoint_lon *= -1.0;  
+
+       /* Compute this track's scaling factors: Used for scaling each track point and then
+          later written out to the track footer. These are approximately the ratios between 
+          pixels and degrees when viewing the 1:24000 map in TOPO!. In practice, it doesn't 
+          appear to be necessary that they be correct, as long as the same values are used 
+          for doing the scaling and for writing into the track footer data. */
+       output_track_lat_scale = 4.8828125e-005; /* TOPO! appears to use a constant lat scale */
+       output_track_lon_scale = output_track_lat_scale / cos(GPS_Math_Deg_To_Rad(first_track_waypoint_lat));
+
+       /* 8 bytes - longitude */
+    tpo_fwrite_double(first_track_waypoint_lon, tpo_file_out);
+
+    /* 8 bytes - latitude */
+    tpo_fwrite_double(first_track_waypoint_lat, tpo_file_out);
+
+    /* 8 bytes: seems to be bounding box info */
+       fwrite(bounding_box, 1, sizeof(bounding_box), tpo_file_out);
+
+       /* number of route points */
+       le_write16(temp_buffer, rte->rte_waypt_ct);
+       fwrite(temp_buffer, 1, 2, tpo_file_out);
+
+       /* initialize the track length computation */
+       track_length = 0;
+       GPS_Math_WGS84LatLonH_To_XYZ(
+               first_track_waypoint->latitude, 
+               first_track_waypoint->longitude,
+               0.0,
+               &last_waypoint_x,
+               &last_waypoint_y,
+               &last_waypoint_z);
+}
+
+static void
+tpo_track_disp(const waypoint *waypointp)
+{
+       double lat, lon, amt, x, y, z;
+       short lat_delta, lon_delta;
+    unsigned char temp_buffer[2];
+       
+       /* convert lat/lon position to XYZ meters */
+       GPS_Math_WGS84LatLonH_To_XYZ(
+               waypointp->latitude, 
+               waypointp->longitude,
+               0.0,
+               &x,
+               &y,
+               &z);
+
+       /* increase the track length by the 3D length of last track segment in feet */
+       track_length += 3.2808 * sqrt(
+               (x - last_waypoint_x) * (x - last_waypoint_x) + 
+               (y - last_waypoint_y) * (y - last_waypoint_y) +
+               (z - last_waypoint_z) * (z - last_waypoint_z));
+       last_waypoint_x = x;
+       last_waypoint_y = y;
+       last_waypoint_z = z;
+
+       /* convert lat/long to NAD27/CONUS datum */
+    GPS_Math_WGS84_To_Known_Datum_M(
+        waypointp->latitude,
+        waypointp->longitude,
+        waypointp->altitude, 
+        &lat,
+        &lon,
+        &amt,
+        78);
+
+    /* swap the sign back *after* the datum conversion */
+    lon *= -1.0;
+
+       /* longitude delta from first route point */
+       lon_delta = (short)((first_track_waypoint_lon - lon) / output_track_lon_scale);
+       le_write16(temp_buffer, lon_delta);
+       fwrite(temp_buffer, 1, 2, tpo_file_out);
+
+       /* latitude delta from first route point */
+       lat_delta = (short)((first_track_waypoint_lat - lat) / output_track_lat_scale);
+       le_write16(temp_buffer, lat_delta);
+       fwrite(temp_buffer, 1, 2, tpo_file_out);
+}
+
+static void
+tpo_track_tlr(const route_head *rte)
+{
+       unsigned char temp_buffer[2];
+
+       unsigned char unknown1[] = { 0x06, 0x00 };
+
+       unsigned char continue_marker[] = { 0x01, 0x80 };
+       unsigned char end_marker[] = { 0x00, 0x00 };
+
+       /* pixel to degree scaling factors */
+       tpo_fwrite_double(output_track_lon_scale, tpo_file_out);
+       tpo_fwrite_double(output_track_lat_scale, tpo_file_out);
+
+    /* 4 bytes: the total length of the route */
+       le_write32(temp_buffer, (unsigned int)track_length);
+    fwrite(temp_buffer, 1, 4, tpo_file_out);
+
+       /* 2 unknown bytes */
+       fwrite(unknown1, 1, sizeof(unknown1), tpo_file_out);
+
+       /* the last track ends with 0x0000 instead of 0x0180 */
+       track_out_count++;
+       if (track_out_count == track_count()) {         
+           fwrite(end_marker, 1, sizeof(end_marker), tpo_file_out);
+       } else {
+           fwrite(continue_marker, 1, sizeof(continue_marker), tpo_file_out);
+       }
+}
+
+static void
+tpo_wr_init(const char *fname)
+{      
+       if (doing_wpts || doing_rtes)
+       {
+               fatal(MYNAME ": this file format only supports tracks, not waypoints or routes.\n");
+       }
+
+       tpo_file_out = xfopen(fname, "wb", MYNAME);
+       tpo_write_file_header();
+}
+
+static void
+tpo_wr_deinit(void)
+{
+       /* the file footer is six bytes of zeroes */
+       unsigned char file_footer_bytes[6];
+       memset(file_footer_bytes, 0, sizeof(file_footer_bytes));
+       fwrite(file_footer_bytes, 1, sizeof(file_footer_bytes), tpo_file_out);
+
+       fclose(tpo_file_out);
+}
+
+static void
+tpo_write(void)
+{
+       unsigned char buffer[8];        
+       unsigned char unknown1[] = { 0xFF, 0xFF, 0x01, 0x00 };
+
+       char* chunk_name = "CTopoRoute";
+       int chunk_name_length = strlen(chunk_name);
+
+       /* write the total number of tracks */
+    le_write16(buffer, track_count());
+       fwrite(buffer, 1, 2, tpo_file_out);
+       
+       /* 4 unknown bytes */
+       fwrite(unknown1, 1, 4, tpo_file_out);
+
+       /* chunk name: "CTopoRoute" */
+       le_write16(buffer, chunk_name_length);
+       fwrite(buffer, 1, 2, tpo_file_out);
+       fwrite(chunk_name, 1, chunk_name_length, tpo_file_out);
+
+       track_out_count = 0;
+       track_disp_all(tpo_track_hdr, tpo_track_tlr, tpo_track_disp);
+}
+
+/* TPO format can read and write tracks only */
+ff_vecs_t tpo_vecs = {
+       ff_type_file,
+       { ff_cap_none | ff_cap_none, ff_cap_read | ff_cap_write, ff_cap_none | ff_cap_none },
+       tpo_rd_init,
+       tpo_wr_init,
+       tpo_rd_deinit,
+       tpo_wr_deinit,
+       tpo_read,
+       tpo_write,
+       NULL,
+       tpo_args,
+       CET_CHARSET_ASCII, 0    /* CET-REVIEW */
+};
index dcb9c052c6bd28d4e6dbc625f15020a7de88db2e..84090e104c73dbfbd71d26d16db0c61af9a3c66c 100644 (file)
@@ -49,6 +49,7 @@ extern ff_vecs_t garmin_vecs;
 extern ff_vecs_t holux_vecs;
 extern ff_vecs_t xcsv_vecs;
 extern ff_vecs_t tpg_vecs;
+extern ff_vecs_t tpo_vecs;
 extern ff_vecs_t magnav_vec;
 extern ff_vecs_t tmpro_vecs;
 extern ff_vecs_t gcdb_vecs;
@@ -210,6 +211,12 @@ vecs_t vec_list[] = {
                "National Geographic Topo .tpg",
                "tpg"
        },
+       {
+               &tpo_vecs,
+               "tpo",
+               "National Geographic Topo .tpo (tracks)",
+               "tpo"
+       },
        {
                &tmpro_vecs,
                "tmpro",